package com.abing.sandbox;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RuntimeUtil;
import com.abing.sandbox.enums.ExecuteCodeStatusEnum;
import com.abing.sandbox.enums.JudgeInfoStatusEnum;
import com.abing.sandbox.request.ExecuteCodeRequest;
import com.abing.sandbox.request.ExecuteCodeResponse;
import com.abing.sandbox.request.JudgeInfo;
import org.springframework.util.StopWatch;

import java.io.File;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import static com.abing.sandbox.constant.SandboxConstants.*;

/**
 * @Author CaptainBing
 * @Date 2024/9/10 16:19
 * @Description
 */
public class AcmLocalCodeSandbox implements CodeSandbox {

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {

        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        String code = executeCodeRequest.getCode();

        // 1.保存代码文件
        File userCodeFile = saveCodeFile(code);

        // 2.编译文件
        String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
        Process compileProcess = RuntimeUtil.exec(compileCmd);
        try {
            int exitValue = compileProcess.waitFor();
            if (exitValue != 0) {
                String errorResult = RuntimeUtil.getResult(compileProcess);
                executeCodeResponse.setStatus(ExecuteCodeStatusEnum.COMPILE_FAILED.getValue());
                executeCodeResponse.setMessage(errorResult);
                executeCodeResponse.setJudgeInfo(new ArrayList<>());
                executeCodeResponse.setOutputList(new ArrayList<>());
                return executeCodeResponse;
            }
        } catch (InterruptedException e) {
            return getErrorResponse(e);
        }

        // 3.执行文件
        List<String> outputList = new ArrayList<>();
        List<JudgeInfo> judgeInfoList = new ArrayList<>();
        String userCodeParentPath = userCodeFile.getParent();
        String runCmd = String.format("java -Dfile.encoding=UTF-8 -cp %s Main", userCodeParentPath);

        List<String> inputs = executeCodeRequest.getInputs();
        for (String input : inputs) {
            input = input + "\n";

            // 计算程序用时
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();

            // 判题信息
            JudgeInfo judgeInfo = new JudgeInfo();
            long totalMemory = RuntimeUtil.getTotalMemory();
            Process runProcess = RuntimeUtil.exec(runCmd);

            try {
                OutputStream outputStream = runProcess.getOutputStream();
                OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
                outputStreamWriter.write(input);
                outputStreamWriter.flush();
                outputStreamWriter.close();
                outputStream.close();
                long freeMemory = RuntimeUtil.getFreeMemory();
                judgeInfo.setMemory(totalMemory - freeMemory);

                outputList.addAll(RuntimeUtil.getResultLines(runProcess));
                stopWatch.stop();
                judgeInfo.setTime(stopWatch.getTotalTimeMillis());

                judgeInfo.setStatus(JudgeInfoStatusEnum.SUCCESS.getValue());
                judgeInfo.setMessage(JudgeInfoStatusEnum.SUCCESS.getText());

                runProcess.getInputStream().close();
                runProcess.destroy();
                judgeInfoList.add(judgeInfo);
            } catch (Exception e) {
                judgeInfo.setStatus(JudgeInfoStatusEnum.FAILED.getValue());
                judgeInfo.setMessage(e.getMessage());
                judgeInfoList.add(judgeInfo);
            }
        }

        // 4.删除文件
        if (Objects.nonNull(userCodeFile.getParentFile())) {
            FileUtil.del(userCodeParentPath);
        }

        executeCodeResponse.setStatus(ExecuteCodeStatusEnum.SUCCESS.getValue());
        executeCodeResponse.setMessage(ExecuteCodeStatusEnum.SUCCESS.getText());
        executeCodeResponse.setJudgeInfo(judgeInfoList);
        executeCodeResponse.setOutputList(outputList);
        return executeCodeResponse;
    }

    /**
     * 响应错误信息
     * @param e
     * @return
     */
    private ExecuteCodeResponse getErrorResponse(Throwable e) {
        // 表示代码沙箱运行错误
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setMessage(e.getMessage());
        executeCodeResponse.setStatus(ExecuteCodeStatusEnum.COMPILE_FAILED.getValue());
        executeCodeResponse.setJudgeInfo(new ArrayList<>());
        executeCodeResponse.setOutputList(new ArrayList<>());
        return executeCodeResponse;
    }

    /**
     * 保存代码文件
     * @param code
     * @return
     */
    private File saveCodeFile(String code){

        String globalCodePathName = USER_DIR + File.separator + GLOBAL_CODE_DIR_NAME;
        // 判断全局代码目录是否存在，没有则新建
        if (!FileUtil.exist(globalCodePathName)) {
            FileUtil.mkdir(globalCodePathName);
        }

        // 把用户的代码隔离存放
        String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
        String userCodePath = userCodeParentPath + File.separator + GLOBAL_JAVA_CLASS_NAME;
        return FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
    }

    public static void main(String[] args) {
        AcmLocalCodeSandbox acmLocalCodeSandbox = new AcmLocalCodeSandbox();
        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputs(Arrays.asList("1 2", "2 4", "4 4"));
        executeCodeRequest.setCode("import java.util.Scanner;\n" +
                                           "\n" +
                                           "public class Main {\n" +
                                           "\tpublic static void main(String[] args) {\n" +
                                           "\t\tScanner scanner = new Scanner(System.in);\n" +
                                           "\t\twhile (scanner.hasNext()) {\n" +
                                           "\t\t\tint a = scanner.nextInt();\n" +
                                           "\t\t\tint b = scanner.nextInt();\n" +
                                           "\t\t\tint c = a + b;\n" +
                                           "\t\t\tSystem.out.println(c);\n" +
                                           "\t\t}\n" +
                                           "\t}\n" +
                                           "}");
        ExecuteCodeResponse executeCodeResponse = acmLocalCodeSandbox.executeCode(executeCodeRequest);
        System.out.println("executeCodeResponse = " + executeCodeResponse);
    }

}
